package mq

import (
	"gitee.com/llakcs/agile-go/log"
	"github.com/streadway/amqp"
	"sync"
)

type RabbitMqOption func(*RabbitMQ)

var global *RabbitMQ

func SetGlobalMq(r *RabbitMQ) {
	var lock sync.Mutex
	lock.Lock()
	defer lock.Unlock()
	global = r
}

func GetGlobalMq() *RabbitMQ {
	return global
}

type RabbitMQ struct {
	Conn    *amqp.Connection
	Channel *amqp.Channel
	//MQ链接字符串
	ConnUrl string
}

func NewRabbitMQ(connUrl string, opts ...RabbitMqOption) (*RabbitMQ, error) {
	r := &RabbitMQ{
		ConnUrl: connUrl,
	}
	for _, o := range opts {
		o(r)
	}
	var err error
	//创建rabbitmq连接
	r.Conn, err = amqp.Dial(r.ConnUrl)
	if err != nil {
		return nil, err
	}
	//创建Channel
	r.Channel, err = r.Conn.Channel()
	if err != nil {
		return nil, err
	}
	return r, nil
}

/*
*
声明交换机
name 交换机名称
kind 交换机类型
durable 持久化标识
autoDelete 是否自动删除
internal 是否是内置交换机
noWait 是否等待服务器确认
args 其它配置
*/
func (r *RabbitMQ) ExchangeDeclare(name, exchangeType string, durable, autoDelete, internal, noWait bool, args amqp.Table) {
	err := r.Channel.ExchangeDeclare(name, exchangeType, durable, autoDelete, internal, noWait, args)
	if err != nil {
		log.Error(err)
		return
	}
}

/*
*
声明队列
name 队列名称
durable 持久化
autoDelete 自动删除
exclusive 排他
noWait 是否等待服务器确认
args Table

死信队列配置

	amqp.Table{
				//"x-message-ttl":             5000,                    // 指定过期时间
				//"x-max-length":              6,						// 指定长度。超过这个长度的消息会发送到dead_exchange中
				"x-dead-letter-exchange":    constant.DeadExchange,    // 指定死信交换机
				"x-dead-letter-routing-key": constant.DeadRoutingKey,  // 指定死信routing-key
			}
*/
func (r *RabbitMQ) QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args amqp.Table) {
	_, err := r.Channel.QueueDeclare(name, durable, autoDelete, exclusive, noWait, args)
	if err != nil {
		log.Error(err)
		return
	}
}

/*
队列绑定
name 队列名称
key BindingKey 根据交换机类型来设定
exchange 交换机名称
noWait 是否等待服务器确认
args Table
*/
func (r *RabbitMQ) QueueBind(name, key, exchange string, noWait bool, args amqp.Table) {
	err := r.Channel.QueueBind(name, key, exchange, noWait, args)
	if err != nil {
		log.Error(err)
		return
	}
}

/*
*
exchange 交换器名称
key RouterKey
mandatory 是否为无法路由的消息进行返回处理
immediate 是否对路由到无消费者队列的消息进行返回处理 RabbitMQ 3.0 废弃
msg 消息体
参数说明要点：

mandatory
消息发布的时候设置消息的 mandatory 属性用于设置消息在发送到交换器之后无法路由到队列的情况对消息的处理方式，设置为 true 表示将消息返回到生产者，否则直接丢弃消息。

immediate
参数告诉服务器至少将该消息路由到一个队列中，否则将消息返回给生产者。 imrnediate 参数告诉服务器，如果该消息关联的队列上有消费者，则立刻投递：如果所有匹配的队列上都没有消费者，则直接将消息返还给生产者，不用将消息存入队列而等待消费者了。

RabbitMQ 3.0版本开始去掉了对 imrnediate 参数的支持。

其中 amqp.Publishing 的 DeliveryMode 如果设为 amqp.Persistent 则消息会持久化。需要注意的是如果需要消息持久化 Queue 也是需要设定为持久化才有效。
*/
func (r *RabbitMQ) Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error {
	return r.Channel.Publish(exchange, key, mandatory, immediate, msg)
}

/*
从队列获取消息
queue 队列名称
consumer 消息者名称
autoAck 是否自动确认消费
exclusive 排他
noLocal
noWait bool
args Table
参数说明要点：
noLocal
设置为 true 则表示不能将同一个 Connection 中生产者发送的消息传送给这个 Connection 中的消费者
其中 autoAck 可以设置为 true 或者 false 。
如果设为 true 则消费者一接收到就从 queue 中去除了，如果消费者处理消息中发生意外该消息就丢失了。
如果设为 false 则消费者在处理完消息后，调用 msg.Ack(false) 后消息才从 queue 中去除。即便当前消费者处理该消息发生意外，只要没有执行 msg.Ack(false) 那该消息就仍然在 queue 中，不会丢失。
如果autoAck设置为 false 则表示需要手动进行 ack 消费
*/
func (r *RabbitMQ) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args amqp.Table) (<-chan amqp.Delivery, error) {
	return r.Channel.Consume(queue, consumer, autoAck, exclusive, noLocal, noWait, args)
}

// 释放资源,建议NewRabbitMQ获取实例后 配合defer使用
func (r *RabbitMQ) ReleaseRes() {
	r.Conn.Close()
	r.Channel.Close()
}
